
package uk.org.cardboardbox.problemmachine;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;

import android.util.Log;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Processor {

    public static final int INST_SHL = 0x0;

    public static final int INST_SHR = 0x1;

    public static final int INST_IN = 0x2;

    public static final int INST_OUT = 0x3;

    public static final int INST_INC = 0x4;

    public static final int INST_DEC = 0x5;

    public static final int INST_ZERO = 0x6;

    public static final int INST_SWAP = 0x7;

    public static final int INST_JMPZ = 0x8;

    public static final int INST_JMPA = 0x9;

    public static final int INST_JMPO = 0xA;

    public static final int INST_JMPT = 0xB;

    public static final int INST_JPZZ = 0xC;

    public static final int INST_JPZA = 0xD;

    public static final int INST_JPZO = 0xE;

    public static final int INST_JPZT = 0xF;

    private static final String TAG = "Processor";

    public static final class SavedState {

        public final long version = 1;

        public final State state;

        public SavedState(State state) {
            this.state = state;
        }

    }

    public static final class State {
        public int[] mem = new int[16];

        public int clocks = 0;

        public int PC = 0;

        public int A = 0;

        public int shadowA = 0;

        public boolean Z = false;

        public int OUT = 0;

        public int IN = 0;

        // to make sure no one has fucked around with stuff..
        protected void clean() {
            for (int i = 0; i < mem.length; i++) {
                mem[i] &= 0xff;
            }
            PC &= 0x0f;
            A &= 0x0f;
            shadowA &= 0x0f;
            OUT &= 0x0f;
            IN &= 0x0f;
        }
    }

    private static final Gson gson;

    static {
        gson = new GsonBuilder().setPrettyPrinting().create();
    }

    private State state = new State();

    private int inc(int arg) {

        int temp = arg;
        temp = temp + 1;
        if (temp > 0xF) {
            temp = 0;
        }

        return temp;
    }

    private int dec(int arg) {

        int temp = arg;
        temp = temp - 1;
        if (temp < 0) {
            temp = 0xF;
        }

        return temp;
    }

    private int shiftLeft(int arg) {

        int temp = arg;
        temp = (temp << 1);
        temp = temp & 0xF;

        return temp;
    }

    private int shiftRight(int arg) {

        int temp = arg;
        temp = (temp >> 1);
        temp = temp & 0xF;

        return temp;
    }

    void reset() {
        state.PC = 0;
        state.A = 0;
        // IN = 0;
        state.OUT = 0;
        state.shadowA = 0;
        state.Z = false;
        state.clocks = 0;
    }

    void step() {
        int opcode = state.mem[state.PC];

        // All instructions except SWAP, JMPA and JPZA take one clock
        state.clocks++;

        switch (opcode) {
            case INST_SHL:
                state.A = shiftLeft(state.A);
                if (state.A == 0) {
                    state.Z = true;
                } else {
                    state.Z = false;
                }
                state.PC = inc(state.PC);

                break;
            case INST_SHR:
                state.A = shiftRight(state.A);
                if (state.A == 0) {
                    state.Z = true;
                } else {
                    state.Z = false;
                }
                state.PC = inc(state.PC);
                break;

            case INST_IN:
                Log.d(TAG, "IN");
                state.A = state.IN;
                state.PC = inc(state.PC);
                break;

            case INST_OUT:
                state.OUT = state.A;
                state.PC = inc(state.PC);
                break;

            case INST_INC:
                state.A = inc(state.A);
                if (state.A == 0) {
                    state.Z = true;
                } else {
                    state.Z = false;
                }
                state.PC = inc(state.PC);
                break;

            case INST_DEC:
                state.A = dec(state.A);
                if (state.A == 0) {
                    state.Z = true;
                } else {
                    state.Z = false;
                }
                state.PC = inc(state.PC);
                break;

            case INST_ZERO:
                state.A = 0;
                state.Z = true;
                state.PC = inc(state.PC);
                break;

            case INST_SWAP:
                int temp;
                temp = state.A;
                state.A = state.shadowA;
                state.shadowA = temp;
                state.PC = inc(state.PC);
                break;

            case INST_JMPZ:
                state.PC = 0;
                break;

            case INST_JMPA:
                state.PC = state.A;
                state.clocks++;
                break;

            case INST_JMPO:
                state.PC = dec(state.PC); // The pc has already been incremented
                                          // by 1
                break;

            case INST_JMPT:
                state.PC = dec(state.PC);
                state.PC = dec(state.PC);
                break;

            case INST_JPZZ:
                if (state.Z != true) {
                    state.PC = 0;
                } else {
                    state.PC = inc(state.PC);
                }
                break;

            case INST_JPZA:
                if (state.Z != true) {
                    state.PC = state.A;
                    state.clocks = state.clocks + 1;
                } else {
                    state.PC = inc(state.PC);
                }
                break;

            case INST_JPZO:
                if (state.Z != true) {
                    state.PC = dec(state.PC);
                } else {
                    state.PC = inc(state.PC);
                }
                break;

            case INST_JPZT:
                if (state.Z != true) {
                    state.PC = dec(state.PC);
                    state.PC = dec(state.PC);
                } else {
                    state.PC = inc(state.PC);
                }
                break;
        }

    }

    int getClocks() {
        return state.clocks;
    }

    int getPC() {
        return state.PC;
    }

    int getA() {
        return state.A;
    }

    int getShadowA() {
        return state.shadowA;
    }

    boolean getZ() {
        return state.Z;
    }

    void putInstruction(int address, int opcode) {
        state.mem[address] = opcode;
    }

    int getInstruction(int address) {
        return state.mem[address];
    }

    int getOut() {
        return state.OUT;
    }

    void setIn(int arg) {
        state.IN = (arg & 0xF);
    }

    int getIn() {
        return state.IN;
    }

    void clear() {
        for (int i = 0; i < state.mem.length; i++) {
            state.mem[i] = 0;
        }
    }

    boolean loadState(File source) {
        try {
            SavedState loadedState = gson.fromJson(new FileReader(source), SavedState.class);
            loadedState.state.clean();
            this.state = loadedState.state;
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    boolean saveState(File dest) {
        String json = gson.toJson(new SavedState(state));
        Log.d(TAG, "state: " + json);
        FileWriter fw;
        try {
            fw = new FileWriter(dest);
            fw.write(json);
            fw.flush();
            fw.close();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}
